﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Geometry;

namespace PlanningGIS.ArcGIS
{
    /// <summary>
    /// 概述：ESRI.ArcGIS.Geodatabase 扩展类<br />
    /// </summary>
    [ComVisible(true)]
    public static class GeodatabaseExtension
    {
        #region IWorkspace扩展
        /// <summary>
        /// 开始一个新的事务。
        /// </summary>
        /// <param name="workspace">工作空间。</param>
        public static void StartTransaction(this IWorkspace workspace)
        {
            (workspace as ITransactions).StartTransaction();
        }

        /// <summary>
        /// 提交当前事务。
        /// </summary>
        /// <param name="workspace">工作空间。</param>
        public static void CommitTransaction(this IWorkspace workspace)
        {
            (workspace as ITransactions).CommitTransaction();
        }

        /// <summary>
        /// 判断当前是否有事务正在进行。
        /// </summary>
        /// <param name="workspace">工作空间。</param>
        public static bool InTransaction(this IWorkspace workspace)
        {
            return (workspace as ITransactions).InTransaction;
        }

        /// <summary>
        /// 中止当前事务。
        /// </summary>
        /// <param name="workspace">工作空间。</param>
        public static void AbortTransaction(this IWorkspace workspace)
        {
            (workspace as ITransactions).AbortTransaction();
        }

        /// <summary>
        /// 获取工作空间中相同的要素类名称，即在要素类名称后面添加【_序号】
        /// </summary>
        /// <param name="workspace">工作空间</param>
        /// <param name="name">数据集名称</param>
        /// <returns></returns>
        public static string GetExistName(this IWorkspace workspace, string name)
        {
            if (workspace == null) return name;
            int index = 0;
            string newName = name;
            while (((IWorkspace2)workspace).get_NameExists(esriDatasetType.esriDTFeatureClass, newName))
            {
                index++;
                newName = name + "_" + index;
            }
            return newName;
        }

        /// <summary>
        /// 函数:判断两个工作空间是否是同一个
        /// </summary>
        /// <param name="wks1">工作空间1</param>
        /// <param name="wks2">工作空间2</param>
        /// <returns>同一个则返回true,否则返回false</returns>
        public static bool Equals2(this IWorkspace wks1, IWorkspace wks2)
        {
            UID pUID1 = wks1.WorkspaceFactory.GetClassID();
            UID pUID2 = wks2.WorkspaceFactory.GetClassID();
            if (pUID1.Value.Equals(pUID2.Value) == false) return false;//判断工作空间类型是否匹配
            if (wks1.Type != wks2.Type) return false;
            if (wks1.Type == esriWorkspaceType.esriFileSystemWorkspace)//Converage, Shapefile
            {
                return wks1.PathName.Equals(wks2.PathName);
            }
            else if (wks1.Type == esriWorkspaceType.esriLocalDatabaseWorkspace) //FileGeodatabase, Personal Geondatabase
            {
                return wks1.PathName.Equals(wks2.PathName);
            }
            else if (wks1.Type == esriWorkspaceType.esriRemoteDatabaseWorkspace)//ArcSDE、 OLE DB
            {
                IPropertySet pPropSet1 = wks1.ConnectionProperties;
                IPropertySet pPropSet2 = wks2.ConnectionProperties;
                if (pPropSet1.Count != pPropSet2.Count) return false;
                object names1 = null;
                object values1 = null;
                pPropSet1.GetAllProperties(out names1, out values1);
                System.Array names1Array = (System.Array)names1;
                for (int i = 0; i < names1Array.Length; i++)
                {
                    string name = Convert.ToString(names1Array.GetValue(i));
                    //如果其它都相等，密码肯定也相等；而且密码的val1和val2是数组，就算相等val2.Equals(val1)还是会返回false
                    if (name.ToUpper() == "PASSWORD") continue;
                    object val1 = pPropSet1.GetProperty(name);
                    object val2 = pPropSet2.GetProperty(name);
                    if (val1.ToString().Equals(val2.ToString()) == false) return false;
                }
            }
            return true;
        }

        /// <summary>
        /// 重新连接到SDE
        /// </summary>
        /// <param name="workspace"></param>
        public static void ReConnection(this IWorkspace workspace)
        {
            if (workspace == null) return;
            //从前面的IFeatureWorkspace对象获取到Factory
            IWorkspaceFactory pWorkspaceFactory = workspace.WorkspaceFactory;
            IWorkspaceFactoryStatus pWSFStatus = (IWorkspaceFactoryStatus)pWorkspaceFactory;
            IEnumWorkspaceStatus enumWSStatus = pWSFStatus.WorkspaceStatus;
            IWorkspace pDownWS = null;
            IWorkspaceStatus pWSStatus = enumWSStatus.Next();
            while (pWSStatus != null)
            {
                if (pWSStatus.ConnectionStatus == esriWorkspaceConnectionStatus.esriWCSDown)
                {
                    for (int i = 0; i < 3; i++) //最多尝试3次
                    {
                        //返回Workspace的状态
                        IWorkspaceStatus currentWSStatus = pWSFStatus.PingWorkspaceStatus(pWSStatus.Workspace);
                        if (currentWSStatus.ConnectionStatus == esriWorkspaceConnectionStatus.esriWCSAvailable)
                        {
                            //如果连接恢复，返回down掉连接的新workspace
                            pDownWS = pWSFStatus.OpenAvailableWorkspace(currentWSStatus);
                            break;
                        }
                        else
                        {
                            System.Threading.Thread.Sleep(1000);      //等待1秒后再继续下一次状态判断
                        }
                    }
                }
                pWSStatus = enumWSStatus.Next();
            }
            IFeatureWorkspace pFeatWS = pDownWS as IFeatureWorkspace;
        }

        #endregion

        #region IFeatureClass扩展
        /// <summary>
        /// 判断要素类是否有Z值
        /// </summary>
        /// <param name="feauteclass"></param>
        /// <returns></returns>
        public static bool HasZValue(this IFeatureClass feauteclass)
        {
            IField fd = feauteclass.Fields.get_Field(feauteclass.Fields.FindField(feauteclass.ShapeFieldName));
            IFieldEdit fdedit = fd as IFieldEdit;
            IGeometryDef geometryDef = fdedit.GeometryDef;
            return geometryDef.HasZ;
        }
        
        /// <summary>
        /// 获取数据集完整的路径名称
        /// </summary>
        /// <param name="feauteclass">数据集</param>
        /// <returns></returns>
        public static string GetFullName(this IFeatureClass feauteclass)
        {
            IDataset dataset = feauteclass as IDataset;
            string path = path = dataset.Workspace.PathName;
            string fullname = "";
            switch (dataset.Workspace.Type)
            {
                case esriWorkspaceType.esriFileSystemWorkspace:
                    if (dataset.Category.Equals("Shapefile Feature Class", StringComparison.CurrentCultureIgnoreCase))
                        fullname = path + "\\" + dataset.Name + ".shp";
                    else if ((dataset.Category.Equals("CAD Point Feature Class", StringComparison.CurrentCultureIgnoreCase)) ||
                        dataset.Category.Equals("CAD Polyline Feature Class", StringComparison.CurrentCultureIgnoreCase) ||
                        dataset.Category.Equals("CAD Polygon Feature Class", StringComparison.CurrentCultureIgnoreCase) ||
                        dataset.Category.Equals("CAD Annotation Feature Class", StringComparison.CurrentCultureIgnoreCase) ||
                        dataset.Category.Equals("CAD MultiPatch Feature Class", StringComparison.CurrentCultureIgnoreCase))
                        fullname = path + "\\" + feauteclass.FeatureDataset.Name + "\\" + dataset.Name;
                    else
                        fullname = path + "\\" + dataset.Name + "\\" + feauteclass.AliasName;
                    break;
                case esriWorkspaceType.esriLocalDatabaseWorkspace:
                case esriWorkspaceType.esriRemoteDatabaseWorkspace:
                    if (feauteclass.FeatureDataset != null)
                        fullname = path + "\\" + feauteclass.FeatureDataset.Name + "\\" + dataset.Name;
                    else
                        fullname = path + "\\" + dataset.Name;
                    break;
                default:
                    fullname = "";
                    break;
            }
            return fullname;
        }
        
        /// <summary>
        /// 修改要素类是否是只加载模式，一般用在入库的时候
        /// </summary>
        /// <param name="feauteclass">要素类</param>
        /// <param name="islock">要素类加载方式，true独占或false共享</param>
        public static void ChangeLoadMode(this IFeatureClass feauteclass, bool islock)
        {
            IDataset ds = feauteclass as IDataset;
            if (ds.Workspace.Type == esriWorkspaceType.esriRemoteDatabaseWorkspace)
            {
                IFeatureClassLoad loadModel = feauteclass as IFeatureClassLoad;
                ISchemaLock schLock = feauteclass as ISchemaLock;
                if (islock)
                {
                    if (loadModel != null) loadModel.LoadOnlyMode = true;
                    schLock.ChangeSchemaLock(esriSchemaLock.esriExclusiveSchemaLock);
                }
                else
                {
                    if (loadModel != null) loadModel.LoadOnlyMode = false;
                    schLock.ChangeSchemaLock(esriSchemaLock.esriSharedSchemaLock);
                }
            }
        }

        /// <summary>
        /// 添加字段
        /// </summary>
        /// <param name="featcls">图层</param>
        /// <param name="fieldName">只读名称</param>
        /// <param name="aliasname"></param>
        /// <param name="filedType"></param>
        /// <param name="fieldLength"></param>
        /// <returns></returns>
        public static bool AddField(this IFeatureClass featcls, string fieldName, string aliasname, esriFieldType filedType, int fieldLength)
        {
            try
            {
                if (featcls.FindField(fieldName) >= 0) return true;
                IFields fields = featcls.Fields;
                IFieldEdit fieldEdit = new FieldClass();
                fieldEdit.Name_2 = fieldName;
                fieldEdit.Type_2 = filedType;
                fieldEdit.Editable_2 = true;
                fieldEdit.AliasName_2 = string.IsNullOrWhiteSpace(aliasname) ? fieldName : aliasname;
                fieldEdit.Length_2 = fieldLength;
                ITable table = (ITable)featcls;
                table.AddField(fieldEdit);
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>   
        /// 删除属性表字段   
        /// </summary>   
        /// <param name="featcls"></param>   
        /// <param name="fieldName"></param>   
        /// <returns></returns>   
        public static bool DeleteField(this IFeatureClass featcls, string fieldName)
        {
            try
            {
                ITable table = (ITable)featcls;
                IFields fields = table.Fields;
                int fieldIndex = fields.FindField(fieldName);
                if (fieldIndex < 0) return true;
                IField field = fields.get_Field(fieldIndex);
                table.DeleteField(field);
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// 获取要素类自定义的类型值。
        /// 注记：0；点：1；线：2；面：3；其他：4；无效：-1；栅格：9；
        /// </summary>
        /// <param name="featcls">要素类对象</param>
        /// <returns></returns>
        public static int GetTypeValue(this IFeatureClass featcls)
        {
            if (featcls == null) return -1;
            if (featcls.FeatureType == esriFeatureType.esriFTAnnotation ||
                featcls.FeatureType == esriFeatureType.esriFTCoverageAnnotation) return 0;
            if (featcls.FeatureType == esriFeatureType.esriFTRasterCatalogItem) return 9;
            if (featcls.FeatureType != esriFeatureType.esriFTSimple) return 4;
            if (featcls.ShapeType == esriGeometryType.esriGeometryPoint ||
                featcls.ShapeType == esriGeometryType.esriGeometryMultipoint)
                return 1;
            else if (featcls.ShapeType == esriGeometryType.esriGeometryPolyline ||
                featcls.ShapeType == esriGeometryType.esriGeometryLine)
                return 2;
            else if (featcls.ShapeType == esriGeometryType.esriGeometryPolygon)
                return 3;
            else
                return 4;
        }

        /// <summary>
        /// 获取图层的自定义类型。
        /// 注记：0；点：1；线：2；面：3；其他：4；无效：-1；栅格：9；
        /// </summary>
        /// <param name="layer">图层对象</param>
        /// <returns></returns>
        public static int GetTypeValue(this ILayer layer)
        {
            if (layer == null) return -1;
            if (layer is IRasterLayer) return 9;
            if (layer is IRasterCatalogDisplayProps) return 9;
            if (layer is ITopologyLayer) return 2;
            if (layer is IFeatureLayer)
            {
                IFeatureClass featcls = (layer as IFeatureLayer).FeatureClass;
                if (featcls != null) return featcls.GetTypeValue();
                else return -1;
            }
            return 4;
        }

        /// <summary>
        /// 获取要素类的空间图层。独立出来是因为使用可能比较频繁。GetLayer最终调用的也是该方法
        /// </summary>
        /// <param name="featcls">要素类</param>
        /// <returns></returns>
        public static IFeatureLayer GetFeatureLayer(this IFeatureClass featcls)
        {
            if (featcls == null) return null;
            string nm = (featcls as IDataset).Name;
            IFeatureLayer lyr = new FeatureLayer();            
            if (featcls.FeatureType == esriFeatureType.esriFTAnnotation)
            {
                lyr = new FDOGraphicsLayer() as IFeatureLayer;                
            }
            lyr.Name = nm;
            lyr.FeatureClass = featcls;
            return lyr;
        }

        /// <summary>
        /// 获取数据集中的所有要素类，以图层组的形式显示。
        /// </summary>
        /// <param name="dataset">数据集</param>
        /// <param name="sort">是否按照点线面进行排序</param>
        /// <returns></returns>
        public static IGroupLayer GetGroupLayer(this IFeatureDataset dataset, bool sort = true)
        {
            List<IFeatureLayer> list = LayerHelper.GetLayersOfDataset(dataset as IFeatureDataset, sort);
            if (list != null && list.Count > 0)
            {
                IGroupLayer grplyr = new GroupLayer();
                foreach (IFeatureLayer itm in list)
                    grplyr.Add(itm);
                grplyr.Name = (dataset as IDataset).Name;
                return grplyr;
            }
            return null;
        }

        /// <summary>
        /// 根据IDataset获取对应的图层，若为IFeatureDataset，则将数据集中的所有要素类组合成IGoupLayer。
        /// 若需要要素集合，则使用LayerHelper.GetLayersOfDataset
        /// </summary>
        /// <param name="dataset">数据对象</param>
        /// <param name="sort">若为IFeatureDataset时，是否按照点线面进行排序</param>
        /// <returns></returns>
        public static ILayer GetLayer(this IDataset dataset, bool sort = true)
        {
            ILayer lyr = null;
            switch (dataset.Type)//根据当前所有的图层的数据集类型加载相应的图层
            {
                case esriDatasetType.esriDTFeatureDataset:
                    IGroupLayer grplyr = GetGroupLayer(dataset as IFeatureDataset, sort);
                    if (grplyr != null) lyr = grplyr as ILayer;                        
                    break;
                case esriDatasetType.esriDTFeatureClass:
                    IFeatureLayer flyr = (dataset as IFeatureClass).GetFeatureLayer();
                    if (flyr != null) lyr = flyr as ILayer;
                    break;
                case esriDatasetType.esriDTRasterDataset:
                    IRasterLayer rstLayer = new RasterLayer();
                    rstLayer.CreateFromDataset(dataset as IRasterDataset);
                    lyr = rstLayer as ILayer;
                    break;
                case esriDatasetType.esriDTRasterCatalog:
                    IFeatureLayer featLayer = new GdbRasterCatalogLayer() as IFeatureLayer;
                    featLayer.FeatureClass = dataset as IFeatureClass;
                    //设置当栅格目录中的图幅在地图上超过9个的时候，以格网来显示，而不显示栅格本身
                    IRasterCatalogDisplayProps rstCatalogPro = featLayer as IRasterCatalogDisplayProps;
                    rstCatalogPro.UseScale = true;//不用数量来控制了,而以比例尺来控制
                    //设置一个比例,在此临界栅格数据将会在框架显示与实际栅格显示之间转换
                    rstCatalogPro.TransitionScale = 80000;
                    lyr = rstCatalogPro as ILayer;
                    break;
                case esriDatasetType.esriDTTopology:
                    ITopologyLayer tpLayer = new TopologyLayer() as ITopologyLayer;
                    tpLayer.Topology = dataset as ITopology;
                    lyr = tpLayer as ILayer;
                    break;
            }
            if (lyr != null) lyr.Name = dataset.Name;
            return lyr;
        }
        #endregion

        /// <summary>
        /// 自定义图层集合排序
        /// </summary>
        /// <param name="layers">图层集合</param>
        /// <param name="point2poly">true:点到面；false：面到点</param>
        public static void Sort2(this List<ILayer> layers, bool point2poly = true)
        {
            layers.Sort((one, two) =>
            {
                int ov = one.GetTypeValue();
                int tv = two.GetTypeValue();
                if (ov > tv) return point2poly ? 1 : -1;
                else if (ov == tv) return 0;
                else return point2poly ? -1 : 1;
            });
        }

        /// <summary>
        /// 自定义图层集合排序
        /// </summary>
        /// <param name="layers">图层集合</param>
        /// <param name="point2poly">true:点到面；false：面到点</param>
        public static void Sort2(this List<IFeatureLayer> layers, bool point2poly = true)
        {
            layers.Sort((one, two) =>
            {
                int ov = one.GetTypeValue();
                int tv = two.GetTypeValue();
                if (ov > tv) return point2poly ? 1 : -1;
                else if (ov == tv) return 0;
                else return point2poly ? -1 : 1;
            });
        }
    }
}